﻿using System.Globalization;
using System.Linq;
using Framework.Core.BootStrapper.WindowsService;
using Framework.Core.Io;
using Framework.Core.Process;
using Framework.Core.Process.Tasks;
using Rhino.Mocks;
using Xunit;

namespace Framework.Tests.Core.Process
{
    public class ProcessControllerTest
    {
        class FakeWindowsService : WindowsService
        {
            public FakeWindowsService()
                : base(10, "fake", "fake", "fakeargs")
            {
            }
        }

        class FakeCommand : ICommand
        {
            public int ProcessId { get { return 0; } }
            public string Name { get { return "Fake"; } }
            public string FileName { get { return "FakePath"; } }
            public string Arguments { get { return "FakArgs"; } }
            public bool IsNative { get { return true; } }
        }

        private readonly MockRepository mockRepository;
        private readonly IProcessRunner processRunnerMock;
        private readonly IIoAdapter ioAdapterMock;
        private readonly IAssemblyAdapter assemblyAdapterMock;
        private readonly ProcessController processController;

        public ProcessControllerTest()
        {
            mockRepository = new MockRepository();
            processRunnerMock = mockRepository.StrictMock<IProcessRunner>();
            ioAdapterMock = mockRepository.StrictMock<IIoAdapter>();
            assemblyAdapterMock = mockRepository.StrictMock<IAssemblyAdapter>();
            processController = new ProcessController(processRunnerMock, ioAdapterMock, assemblyAdapterMock);
        }

        [Fact]
        public void Should_start_windows_service()
        {
            var fakeProcess = new FakeWindowsService();
            var result = new ProcessResult(0, string.Empty, string.Empty, false);
            processRunnerMock.Expect(x => x.Start(new Sc.ScStart(fakeProcess.Name), 10)).Return(result);
            mockRepository.ReplayAll();
            var processResult = processController.Start(fakeProcess, 10);
            mockRepository.VerifyAll();

            Assert.Equal(result, processResult);
        }

        [Fact]
        public void Should_start_process()
        {
            var fakeProcess = new FakeCommand();
            var result = new ProcessResult(0, string.Empty, string.Empty, false);
            processRunnerMock.Expect(x => x.Start(fakeProcess, 10)).Return(result);
            mockRepository.ReplayAll();
            var processResult = processController.Start(fakeProcess, 10);
            mockRepository.VerifyAll();

            Assert.Equal(result, processResult);
        }

        [Fact]
        public void Should_skip_kill_handle_when_directory_does_not_exists()
        {
            const string path = @"D:\_Tools";
            ioAdapterMock.Expect(x => x.FileExists(path)).Return(false);
            ioAdapterMock.Expect(x => x.DirectoryExists(path)).Return(false);

            mockRepository.ReplayAll();
            var result = processController.KillLocker(path);
            mockRepository.VerifyAll();

            Assert.Empty(result);
        }

        [Fact]
        public void Should_kill_handle()
        {
            const string path = @"D:\_Tools";
            const string output = @"
Handle v3.42
Copyright (C) 1997-2008 Mark Russinovich
Sysinternals - www.sysinternals.com
RTSApp.exe         pid: 7420      C: D:\_Tools\Royal TS
RTSApp.exe         pid: 7420    248: D:\_Tools\Royal TS\AxInterop.MSTSCLib.dll
RTSApp.exe         pid: 7420    2F0: D:\_Tools\Royal TS\Interop.MSTSCLib.dll
toto.exe           pid: 13      2F0: D:\_Tools\Royal TS\Interop.MSTSCLib.dll
";
            processRunnerMock
                .Expect(x =>
                        {
                            const string directory = path + @"\";
                            return x.Start(new Handle(directory));
                        })
                .Return(new ProcessResult(0, output, string.Empty, false));

            processRunnerMock
                .Expect(x => x.Start(new TaskList()))
                .Return(new ProcessResult(0, string.Join(System.Environment.NewLine, new[]
                {
                    GetOutputTaskList("header", 10), 
                    GetOutputTaskList("RTSApp", 7420), 
                    GetOutputTaskList("devenv", 10)
                }), string.Empty, false));

            processRunnerMock
                .Expect(x => x.Start(new TaskKill(7420)))
                .Return(new ProcessResult(0, string.Empty, string.Empty, false));

            ioAdapterMock.Expect(x => x.FileExists(path)).Return(true);
            ioAdapterMock.Expect(x => x.GetFileDirectory(path)).Return(path);
            ioAdapterMock.Expect(x => x.DirectoryExists(path)).Return(true);

            mockRepository.ReplayAll();
            var result = processController.KillLocker(path).ToList();
            mockRepository.VerifyAll();

            Assert.Equal(1, result.Count);
            Assert.True(result.Any(x => x.Name == "RTSApp"));
        }

        [Fact]
        public void Should_ignore_when_kill_failed()
        {
            processRunnerMock
                .Expect(x => x.Start(new TaskKill(1)))
                .Throw(new ProcessResultException(new ProcessResult(-1, string.Empty, string.Empty, false)));

            mockRepository.ReplayAll();
            processController.Kill(new Framework.Core.Process.Process(1, "n", "n", "", false));
            mockRepository.VerifyAll();
        }

        [Fact]
        public void Should_install_windows_service()
        {
            const string serviceName = "wcfHost";
            const string displayName = "displayName";
            const string args = "args";
            var assembly = typeof(ProcessControllerTest).Assembly;
            var binPath = assembly.Location;
            const string user = "user";
            const string password = "password";
            var windowsServiceArgs = new WindowsServiceArgs
                                     {
                                         Name = serviceName,
                                         DisplayName = displayName,
                                         Args = args,
                                         User = user,
                                         Password = password
                                     };

            MockUninstall(serviceName, false);
            assemblyAdapterMock.Expect(x => x.GetEntryAssembly()).Return(assembly);
            processRunnerMock
                .Expect(x => x.Start(new Sc.ScCreate(serviceName, displayName, binPath, args, user, password)))
                .Return(new ProcessResult(0, string.Empty, string.Empty, false));

            processRunnerMock
                .Expect(x => x.Start(new Sc.ScStart(serviceName)))
                .Return(new ProcessResult(0, string.Empty, string.Empty, false));

            mockRepository.ReplayAll();
            processController.Install(windowsServiceArgs);
            mockRepository.VerifyAll();
        }

        [Fact]
        public void Should_uninstall_windows_service()
        {
            const string serviceName = "wcfHost";
            MockUninstall(serviceName, false);

            mockRepository.ReplayAll();
            processController.Uninstall(new WindowsServiceArgs { Name = serviceName });
            mockRepository.VerifyAll();
        }

        [Fact]
        public void Should_uninstall_windows_service_when_stop_hang()
        {
            const string serviceName = "wcfHost";
            MockUninstall(serviceName, true);

            processRunnerMock
                .Expect(x => x.Start(new TaskKill(608)))
                .Return(new ProcessResult(0, string.Empty, string.Empty, false));

            mockRepository.ReplayAll();
            processController.Uninstall(new WindowsServiceArgs { Name = serviceName });
            mockRepository.VerifyAll();
        }

        private void MockUninstall(string serviceName, bool isTimeoutStopService)
        {
            processRunnerMock
                .Expect(x => x.Start(new TaskList()))
                .Return(GetExeTaskListResult("mmc", 1, string.Empty, string.Empty));

            processRunnerMock
                .Expect(x => x.Start(new TaskKill(1)))
                .Return(new ProcessResult(0, string.Empty, string.Empty, false));

            processRunnerMock
                .Expect(x => x.Start(new TaskList()))
                .Return(GetServiceTaskListResult("svc.exe", serviceName, 608, string.Empty, string.Empty))
                .Repeat.Times((isTimeoutStopService) ? 2 : 1);

            processRunnerMock
                .Expect(x => x.Start(new NetStop(serviceName), 20000))
                .Return(new ProcessResult(0, string.Empty, string.Empty, isTimeoutStopService));

            processRunnerMock
                .Expect(x => x.Start(new Sc.ScDelete(serviceName)))
                .Return(new ProcessResult(0, string.Empty, string.Empty, false));
        }

        [Fact]
        public void Should_have_process()
        {
            processRunnerMock
                .Expect(x => x.Start(new TaskList()))
                .Return(GetExeTaskListResult("mmc", 1, string.Empty, string.Empty));

            mockRepository.ReplayAll();
            var processStates = processController.GetProcesses().ToList();
            Assert.NotEmpty(processStates);
            Assert.Equal("mmc", processStates.First().Name);
            mockRepository.VerifyAll();
        }

        [Fact]
        public void Should_have_constructor_without_args()
        {
            Assert.NotNull(new ProcessController());
        }

        [Fact]
        public void Should_have_list_of_services()
        {
            var processResult = GetServiceTaskListResult("AeLookupSvc", "AeLookupSvc", 608, "AeLookupSvcPath", "AeLookupSvcArgs");
            processRunnerMock
                .Expect(x => x.Start(new TaskList()))
                .Return(processResult);

            mockRepository.ReplayAll();
            var windowsService = processController.GetServices().First(s => s.Name == "AeLookupSvc");
            mockRepository.VerifyAll();
            Assert.Equal("AeLookupSvc", windowsService.Name);
            Assert.Equal(608, windowsService.ProcessId);
            Assert.True(windowsService.IsNative);
        }

        private static ProcessResult GetExeTaskListResult(string name, int pid, string path, string args)
        {
            return GetServiceTaskListResult(name, string.Empty, pid, path, args);
        }
        private static ProcessResult GetServiceTaskListResult(string name, string serviceName, int pid, string path, string args)
        {
            return new ProcessResult(0, GetOutputTaskList(name, serviceName, pid, path, args), string.Empty, false);
        }

        private static string GetOutputTaskList(string name, string serviceName, int pid, string path, string args)
        {
            var strings = new[]
                          {
                              GetLine("header", "", 0, "", ""),
                              GetLine(name, serviceName, pid, path, args)
                          };

            return string.Join(System.Environment.NewLine, strings);
        }
        private static string GetLine(string name, string serviceName, int pid, string path, string args)
        {
            var strings = new[]
                          {
                              name,
                              serviceName,
                              pid.ToString(CultureInfo.InvariantCulture),
                              name,
                              path,
                              args
                          };
            return string.Join("\t", strings);
        }

        private static string GetOutputTaskList(string name, int pid)
        {
            var strings = new[]
                          {
                              GetOutputTaskList("header", string.Empty, 1, string.Empty, string.Empty),
                              GetOutputTaskList(name, string.Empty, pid, string.Empty, string.Empty)
                          };

            return string.Join("\t", strings);
        }
    }
}
